package com.alibaba.dubbo.dynamic.service;

import com.alibaba.dubbo.common.bytecode.ClassGenerator;
import com.alibaba.dubbo.common.compiler.support.JavassistCompiler;
import com.alibaba.dubbo.common.utils.ClassHelper;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.ServiceBean;
import com.alibaba.dubbo.rpc.RpcException;
import javassist.ClassPool;
import javassist.LoaderClassPath;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by wuyu on 2016/8/5.
 */
public class DynamicExportServiceImpl implements DynamicExportService, DisposableBean, ApplicationContextAware {

    private JavassistCompiler javassistCompiler = new JavassistCompiler();

    private ApplicationContext applicationContext;

    private Map<String, ServiceBean> serviceBeanMap = new ConcurrentHashMap<>();

    public <T> Map<String, Object> export(String[] jars, String id, String iFaceClazzName,
                                          String implClazzName, String group, String version, Integer timeout, String filter) {

        Map<String, Map<String, Object>> services = new HashMap<>();
        Map<String, Object> service = new HashMap<>();
        service.put("iFaceClazzName", iFaceClazzName);
        service.put("implClazzName", implClazzName);
        service.put("group", group);
        service.put("version", version);
        service.put("timeout", timeout);
        service.put("filter", filter);
        services.put(id, service);
        return export(jars, services);
    }

    public <T> Map<String, Object> export(String[] jars, Map<String, Map<String, Object>> services) {
        Map<String, Object> map = new LinkedHashMap<>();

        if (services.keySet().size() == 0) {
            return map;
        }

        URLClassLoader urlClassLoader = getUrlClassLoader(jars);
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        ClassPool classPool = ClassGenerator.getClassPool(contextClassLoader);
        LoaderClassPath loaderClassPath = new LoaderClassPath(urlClassLoader);
        classPool.insertClassPath(loaderClassPath);
        Thread.currentThread().setContextClassLoader(urlClassLoader);
        Class iFaceClazz = null;
        Class implClazz = null;


        for (String id : services.keySet()) {
            Map<String, Object> service = services.get(id);
            String iFaceClazzName = (String) service.get("iFaceClazzName");
            String implClazzName = (String) service.get("implClazzName");
            String group = (String) service.get("group");
            String version = (String) service.get("version");
            String filter = (String) service.get("filter");
            Integer timeout = (Integer) service.get("timeout");
            ServiceBean serviceBean = serviceBeanMap.get(id);
            if (serviceBean == null) {
                try {
                    iFaceClazz = Class.forName(iFaceClazzName, true, urlClassLoader);
                    implClazz = Class.forName(implClazzName, true, urlClassLoader);
                } catch (Exception e) {
                    map.put(id, e.getMessage());
                    continue;
                }
                T t = (T) register(implClazz.getName(), implClazz);
                export(id, t, iFaceClazz, group, version, timeout, filter);
                map.put(id, "success");
            }


        }
        return map;
    }


    public URLClassLoader getUrlClassLoader(String[] jarUrls) {
        URL[] urls = new URL[jarUrls.length];
        for (int i = 0; i < jarUrls.length; i++) {
            try {
                urls[i] = new URL(jarUrls[i]);
            } catch (Exception e) {
                throw new RpcException(e);
            }
        }
        return URLClassLoader.newInstance(urls, Thread.currentThread().getContextClassLoader());
    }


    public <T> T register(String id, Class<T> clazz) {
        ConfigurableApplicationContext application = getConfigurableApplicationContext();
        ConfigurableListableBeanFactory beanFactory = application.getBeanFactory();
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(clazz)
                .getBeanDefinition();
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
        defaultListableBeanFactory.registerBeanDefinition(id, beanDefinition);
        return (T) beanFactory.getBean(id);
    }


    private <T> void export(String id, T t, Class iface, String group, String version, Integer timeout, String filter) {
        ServiceBean<T> serviceBean = new ServiceBean<T>();
        if (iface == null) {
            Class<?>[] interfaces = t.getClass().getInterfaces();
            if (interfaces.length > 0) {
                serviceBean.setInterface(interfaces[0]);
            } else {
                throw new RpcException("the " + t.getClass().getName() + " Must implement the interface!");
            }
        } else {
            serviceBean.setInterface(iface);
        }
        serviceBean.setApplicationContext(this.applicationContext);
        serviceBean.setRef(t);
        serviceBean.setDelay(1);
        serviceBean.setGroup(group);
        serviceBean.setVersion(version);
        serviceBean.setTimeout(timeout);
        serviceBean.setFilter(filter);

        try {
            serviceBean.afterPropertiesSet();
            serviceBeanMap.put(id, serviceBean);
        } catch (Exception e) {
            throw new RpcException(e);
        }
    }


    public void compile(String code) {
        javassistCompiler.compile(code, ClassHelper.getCallerClassLoader(getClass()));
    }

    private ConfigurableApplicationContext getConfigurableApplicationContext() {
        return (ConfigurableApplicationContext) this.applicationContext;
    }

    public List<Map<String, Object>> listDynamicBean() {
        List<Map<String, Object>> maps = new ArrayList<>();
        Map<String, Object> map = new LinkedHashMap<>();
        for (String id : serviceBeanMap.keySet()) {
            ServiceBean serviceBean = serviceBeanMap.get(id);
            ApplicationConfig application = serviceBean.getApplication();
            RegistryConfig registry = serviceBean.getRegistry();
            map.put("id", id);
            map.put("impl", serviceBean.getRef().getClass().getName());
            map.put("interface", serviceBean.getInterfaceClass().getName());
            map.put("application", application.getName());
            map.put("registry", registry.getAddress());
            map.put("group", serviceBean.getGroup());
            map.put("version", serviceBean.getVersion());
            map.put("timeout", serviceBean.getTimeout());
            map.put("filter", serviceBean.getFilter());
            maps.add(map);
        }
        return maps;
    }

    public void unexport(List<String> ids) {
        for (String id : ids) {
            ServiceBean serviceBean = serviceBeanMap.remove(id);
            if (serviceBean != null) {
                serviceBean.unexport();
            }
        }
    }


    public void destroy() throws Exception {
        serviceBeanMap.clear();
        applicationContext = null;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}
